package org.kjs.common.clone.implement;

import org.kjs.common.cache.Cache;
import org.kjs.common.clone.AccessibleFieldStructure;
import org.kjs.common.clone.Clone;
import org.kjs.common.clone.CloneableStructure;
import org.kjs.common.clone.util.CloneEngine;
import org.kjs.common.clone.util.implement.CloneEngineer;
import org.kjs.common.reflect.utils.ReflectUtil;
import org.kjs.common.clone.exception.CloneNotSupportedException;

import java.util.*;

/**
 * org.kjs.common.clone.Clone 's implement.
 */
public class Cloner implements Clone, Cache {

    private static final Map<Class<?>,List<CloneableStructure<?>>> cloneCache =
            new HashMap<Class<?>,List<CloneableStructure<?>>>();

    private CloneEngine cloneEngine;

    public Cloner () {
        this(new CloneEngineer());
    }

    public Cloner (CloneEngine cloneEngine) {
        this.cloneEngine = cloneEngine;
    }

    public <V> V clone(V original) throws CloneNotSupportedException {
        V clone = null;

        if (null != original) {
            Class<V> clazz = (Class<V>) original.getClass();
            if (clazz.isArray()) {
                clone = cloneEngine.cloneArray(original,this);
            } else if (clazz.isEnum()) {
                clone = original;
            } else if (original instanceof Collection) {
                clone = cloneEngine.cloneCollection(original,this);
            } else if (original instanceof Map) {
                clone = cloneEngine.cloneMap(original,this);
            } else if (original instanceof String) {
                String s = (String)original;
                clone = (V) new String(s.getBytes());
            } else if (cloneEngine.isJavaBasicDataType(original)) {
                clone = original;
            } else if (cloneEngine.isJavaBasicDataWrapperType(original)) {
                clone = cloneEngine.cloneJavaBasicDataWrapper(original);
            } else if (original instanceof Class) {
                clone = original;
            } else {
                CloneableStructurer<V> cloneableStructurer = new CloneableStructurer<V>(clazz);
                clone = cloneableStructurer.getCloneableConstruct().newCloneableInstance();
                for (AccessibleFieldStructure<V> accessibleField : cloneableStructurer.getAllAccessibleFields()) {
                    accessibleField.cloneAccessibleField(original,clone,this);
                }
            }
        }

        return clone;
    }

    public <V> boolean isDirectCloneObject(V object) {
        boolean isDirectClone = true;

        if (null != object) {
            Class clazz = object.getClass();

            if (clazz.isArray()) {
                Object [] array = (Object []) object;
                if (array.length == 0) {
                    isDirectClone = true;
                } else {
                    Object arrayContainsValue = array[0];
                    isDirectClone = ReflectUtil.isJAVABasicDataType(arrayContainsValue);
                }
            } else if (clazz.isEnum()) {
                isDirectClone = true;
            } else {
                isDirectClone = ReflectUtil.isJAVABasicDataType(object);
            }
        }

        return isDirectClone;
    }

    public void initCache() {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public void resetCache() {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public void cleanCache() {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public <O> void cleanCache(O key) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public boolean isCacheAvailable() {
        return false;  //To change body of implemented methods use File | Settings | File Templates.
    }

    public CloneEngine getCloneEngine() {
        return cloneEngine;
    }

    public void setCloneEngine(CloneEngine cloneEngine) {
        this.cloneEngine = cloneEngine;
    }
}
